// define this to get the VME instead of the USB version
#define LOGIC_VME

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <fcntl.h>
using namespace std;

#ifdef LOGIC_VME
#include "LogicPool/LogicInterfaceVME.h"
#else
#include "LogicPool/LogicInterfaceUSB.h"
#endif
#include "LogicPool/LogicQDC.h"
#include "LogicPool/LogicIndicator.h"
#include "LogicPool/LogicDIO.h"
#include "LogicPool/LogicLogic.h"
#include "LogicPool/LogicCounter.h"


#ifdef LOGIC_VME
const char *dev_fn = (char *) "/dev/sis1100_00remote";
#define LOGICBOX 0xb00000
#else
#define LOGICBOX "/dev/ttyUSB0"
#endif


LogicInterface * lbox;
LogicIndicator * led[8]; 
LogicDIO * dio[8];
LogicQDC * qdc[3];
LogicLogic * log[8];
LogicCounter * counter[2];


void ledtest()
{
 	int i;

    cout << "LED test: running light" << endl;
    cout << "Abbruch mit Ctrl-C" << endl;
	while (1) {
    	for (i=1; i<=8; i++) {
#if 0
			lbox->SetAddress('I', i, 0);
			lbox->WriteByte(0xff);
			usleep(100000);
			lbox->WriteByte(0x80);
#elif 0
		    led[i-1]->SetInput(LogicPool::level_high);
			usleep(100000);
			led[i-1]->SetInput(LogicPool::level_low);
#else
			LogicHigh >> (*led[i-1]);
			usleep(100000);
			(*led[i-1]) << LogicLow;
#endif // 0
		};
	};
}


void iotest()
{
  cout << "output test: create oscillating signal on all channels and LEDs" << endl;
  uint8_t state = LogicPool::level_low;
  while (1) {
    // switch state
	state = (state == LogicPool::level_low) ? LogicPool::level_high : LogicPool::level_low;

    for (int i=1; i<=8; i++) {
	  led[i-1]->SetInput(state);
	  dio[i-1]->SetInput(state);
	}
	usleep(100000);
  };
}


void iotest2()
{
  // connect dio4 and 5 for this test
  cout << "connect DIO 4 and 5 for this test:" << endl;
  cout << "channel 4 and indicator 4 are used as output" << endl;
  cout << "channel 5 is used as input" << endl;
  cout << "indicator 5 and channel 7 reflect the input in channel 5" << endl;

  // use channel 5 as input, activate termination
  dio[4]->SetTermination(true);
  // the following set of statements are equivalent
#if 0
  lbox->SetAddress('T', 5, 0);
  lbox->WriteByte(0);
  uint8_t mux = lbox->ReadByte();
  cout << "mux :" << (int)mux << endl;
  lbox->SetAddress('T', 7, 0);
  lbox->WriteByte(mux);
  lbox->SetAddress('I', 5, 0);
  lbox->WriteByte(mux);
#elif 0
  dio[6]->SetInput(dio[3]->GetOutput());
  led[3]->SetInput(dio[3]->GetOutput());
#else
  // use channel 7 to output the signal again
  *dio[4] >> *dio[6];
  // use led 5 to signal the input
  *dio[4] >> *led[4];
#endif

  uint8_t state = LogicPool::level_low;
  while (1) {
    // switch state
	state = (state == LogicPool::level_low) ? LogicPool::level_high : LogicPool::level_low;
    led[3]->SetInput(state);
	dio[3]->SetInput(state);

	// read state of DIO 5
    bool state = dio[4]->GetState();
	cout << hex << (int) state << " " << flush;

	usleep(200000);
  }
}


void iotest3()
{
  int i;
  cout << "indicator i should reflect input channel i, all channels terminated" << endl;

  for (i=1; i<=8; i++) {
	LogicNC >> *dio[i-1];
	dio[i-1]->SetTermination();
	*led[i-1] << *dio[i-1];
  }

  cout << "sampling DIO 4 (NIM): ";
  while (1) {
	cout << hex << (int) dio[3]->GetState() << " " << flush;
	usleep(200000);
  }
}


void pulsetest()
{
  cout << "create 10KHz clock on dio 7, led 7" << endl;

  // set up NIM output test
  LogicCounter pulser = *counter[0];
  LogicDIO & dio_pulse = *dio[6];
  LogicIndicator & led_pulse = *led[6];
  
  // set pulser frequency to 10kHz
  pulser.SetPulserFrequency(10000);

  // connect pulser output to DIO and LED
  pulser >> led_pulse;
  pulser >> dio_pulse;
}


void logictest()
{
  cout << "logic test: led4 = dio4 OR dio5, led5 = dio4 AND dio5" << endl;
  log[0]->SetMode(LogicLogic::MODE_OR);
  log[1]->SetMode(LogicLogic::MODE_AND);
  for (int i=3; i<7; i++)
  	dio[3]->SetTermination();

  *dio[3] >> log[0]->Channel(0);
  *dio[4] >> log[0]->Channel(1);

  *dio[3] >> log[1]->Channel(0);
  *dio[4] >> log[1]->Channel(1);

  *log[0] >> *led[3];
  *log[1] >> *led[4];
  
  exit(1);
}


void qdcscan()
{
    FILE *of;
    int stop, start, mode, gate;

    gate = 30;

    of = fopen("logic_parameter_scan_gate300_modes_fix.dat", "w+");

    for (mode = 0; mode < 3; mode ++) {
        for (start = 0; start < 255; start ++) {
            cout << "start = " << start << endl;
            for (stop = 0; stop < 255; stop ++) {

                const int max_events = 10240;
                int16_t events1[max_events], events2[max_events], events3[max_events];
                int i, n, av[3];

                // set QDC parameters
                for (int i=0; i<3; i++) {
                    qdc[i]->SetStartDelay(start);
                    qdc[i]->SetStopDelay(stop);
                    qdc[i]->SetGateTime(gate);
                    qdc[i]->SetMode(mode);
                }

                // wait and see
                usleep(1e4);

                // read all events
                n = qdc[0]->GetNEvents();
                    
                bzero(events1, sizeof(events1));
                qdc[0]->ReadEvents(events1, n);
                bzero(events2, sizeof(events2));
                qdc[1]->ReadEvents(events2, n);
                bzero(events3, sizeof(events3));
                qdc[1]->ReadEvents(events3, n);

                av[0] = av[1] = av[2] = 0;
                // average over all events
                for (i=1; i < n; i++) {
                    av[0] += events1[i];
                    av[1] += events2[i];
                    av[2] += events3[i];
                } 
                // save to file 
                fprintf(of, "%i %i %i %i %.1f %.1f %.1f\n", start, stop, mode, n, av[0]/(double)n, av[1]/(double)n, av[2]/(double)n);  
            }
            fprintf(of, "\n");
            fflush(of);
        }
        fprintf(of, "\n");
    }
    fclose(of);
}


void qdcinit()
{
  LogicDIO & dio_trigger = *dio[3];  	//  Trigger input
  LogicDIO & dio_gate    = *dio[4];		//  Gate output

  // connect all triggers to the same dio
  dio_trigger.SetTermination();
  dio_trigger >> *qdc[0];
  dio_trigger >> *qdc[1];
  dio_trigger >> *qdc[2];
  dio_trigger >> *led[3];

  // display single busy signal
  *qdc[0] >> *led[0];
  *qdc[1] >> *led[1];
  *qdc[2] >> *led[2];

  // generate busy signal
  // set all mode to "or"
  log[0]->SetMode(LogicLogic::MODE_OR);
  log[1]->SetMode(LogicLogic::MODE_OR);
  log[2]->SetMode(LogicLogic::MODE_OR);
  
  // log[0] = busy1 or busy2
  log[0]->Channel(0) << *qdc[0];
  log[0]->Channel(1) << *qdc[1];

  // log[1] = busy2 or busy3
  log[1]->Channel(0) << *qdc[1];
  log[1]->Channel(1) << *qdc[2];

  // *log[0] = *log[0] or log2
  log[0]->Channel(0) >> log[2]->Channel(0);
  log[2]->Channel(1) << log[1]->Channel(0);
  
  //dio_gate.SetInput(log3.GetOutput());
  dio_gate << *log[2]; 
  //led2.SetInput(log3.GetOutput());
  *led[4] << *log[2];
  
  // initialize the qdc modules
  qdc[0]->Init();
  qdc[1]->Init();
  qdc[2]->Init();
}


// *************************************
// main
// *************************************


int main(int argc, char **argv)
{
  int i;
#ifdef LOGIC_VME
  int crate;
  
  // open sis1100/3100 pci-to-vme link
  if ( !(crate = open(dev_fn, O_RDWR, 0))) {
    cerr << "ERROR: could not open \"" << dev_fn << "\"" << endl;
    cerr.flush();
    exit(1);
  }
  
  lbox = new LogicInterfaceVME(crate, LOGICBOX);
#else
  lbox = new LogicInterfaceUSB(LOGICBOX);
#endif

  lbox->Open();
  lbox->Reset();

#if 1
  printf("LogicBox ID: %0x\n", lbox->GetID());
  printf("configuration: %0i\n", lbox->GetConfiguration());
  uint8_t major, minor;
  lbox->GetFirmwareVersion(major, minor);
  printf("firmware version: %i.%i\n", major, minor);
  lbox->ListDevices();
#endif

  // create all necessary LogicPool objects
  for (i=1; i<=3; i++)
	qdc[i-1] =  new LogicQDC(*lbox, i);
  for (i=1; i<=8; i++) {
	dio[i-1] = new LogicDIO(*lbox, i);
	led[i-1] = new LogicIndicator(*lbox, i);
	log[i-1] = new LogicLogic(*lbox, i);
  }
  for (i=1; i<=2; i++)
    counter[i-1] = new LogicCounter(*lbox, i);

  // setup IO connectors
  for (i=1; i<=8; i++) {
    dio[i-1]->SetTermination(false);
	dio[i-1]->SetInput(0);  // reset input channel
  }
  for (i=4; i<=8; i++)
	dio[i-1]->SetNIM();

/*    
  logictest();
  ledtest();
  iotest();
  iotest2();
  iotest3();
*/

  // setup 10kHz clock on channel 7
  pulsetest();
  
  // setup QDC board: trigger on channel 4, busy on channel 5
  qdcinit();
  
  // scan start-stop parameter space
  //qdcscan();
  
  const int max_events = 102400;
  int16_t events1[max_events], events2[max_events], events3[max_events];
  FILE *of;
  int num=0;

  of = fopen("logic_test.dat", "w+");
  while (1) {
	int stop, start;
    int i, n;
    int av[3];
    int16_t max[3], min[3];

    start = 28;
    stop  = 16;

    // set QDC parameters
    for (int i=0; i<3; i++) {
      qdc[i]->SetStartDelay(start);
      qdc[i]->SetStopDelay(stop);
    }

    // wait and see
    usleep(1e6);

    // read all events (~15ms per call on USB)
    n = qdc[0]->GetNEvents();
    fprintf(stdout, "%s %i\n","n=",n);
    
    if (qdc[0]->Overflow())
    //if (n >= 1024) // size of buffer is 1024
      printf("WARNING: buffer overflow!\n");
    
    bzero(events1, sizeof(events1));
    qdc[0]->ReadEvents(events1, n);
    bzero(events2, sizeof(events2));
    qdc[1]->ReadEvents(events2, n);
    bzero(events3, sizeof(events3));
    qdc[1]->ReadEvents(events3, n);

#if 1
    for (i=1; i < n; i++)
      fprintf(of, "%i %i %i %i\n", num, events1[i], events2[i], events3[i]);
    num += n;
    if (num >= 50000) break;
#elif 0
	av[0] = av[1] = av[2] = 0;
    // average over all events
    for (i=1; i < n; i++) {
        av[0] += events1[i];
        av[1] += events2[i];
        av[2] += events3[i];
#if 0
        if (i==0) { 
            max=min=events1[i];
        } else {
            if (events1[i] > max) max=events1[i];
            if (events1[i] < min) min=events1[i];
        }
#endif
        num++;
    } 
    // save to file 
    fprintf(of, "%i %.1f %.1f %.1f\n", n, av[0]/(double)n, av[1]/(double)n, av[2]/(double)n);
#endif
    
    fflush(of);
  }
  fclose(of);
  
  lbox->Close();

#ifdef LOGIC_VME
  close(crate);
#endif

  return 0;
}
